package com.example.demo.common.exception;

import com.alibaba.fastjson.JSONException;
import com.example.demo.common.ResultCode;
import com.example.demo.common.Rs;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.exceptions.TooManyResultsException;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.dao.DataAccessException;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.stereotype.Component;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.sql.SQLException;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 全局异常处理类.
 *
 * @author Mei Ruoxiao
 * @since 2020/8/22
 */
@Slf4j
@Component//spring注解中@component就是说把这个bai类交给Spring管理
@ControllerAdvice//使用 @ControllerAdvice 实现全局异常处理，只需要定义类
@ConditionalOnWebApplication//当Spring为web服务时，才使注解的类生效；通常是配置类
public class GlobalExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    @ResponseStatus
    //针对可能出问题的Controller，新增注解方法@ExceptionHandler.
    public ResponseEntity<Rs<Object>> handle(HttpServletRequest request, Exception exception) {
        log.error(request.getRequestURL().toString());
        // 通用异常
        //Integer integer = new Integer(1);
        //System.out.println(integer instanceof  Integer);//true
        if (exception instanceof CommonException) {// instanceof
            handleError(request, exception);
            final ResultCode resultCode = ((CommonException) exception).getResultCode();
            return Objects.isNull(resultCode) ? Rs.fail(exception.getMessage()) : Rs.fail(resultCode);
        } else if (exception instanceof MethodArgumentTypeMismatchException) {
            // 参数为枚举类型时,枚举值无效.
            if (exception.getCause() instanceof ConversionFailedException
                    && exception.getCause().getCause() instanceof CommonException) {
                final CommonException commonException = (CommonException) exception.getCause().getCause();
                log.warn(commonException.getMessage(), commonException);
                final ResultCode resultCode = commonException.getResultCode();
                return Objects.isNull(resultCode) ? Rs.fail(exception.getMessage()) : Rs.fail(resultCode);
            }
            // 方法参数格式不匹配
            handleError(request, exception);
            final String fieldName = ((MethodArgumentTypeMismatchException) exception).getName();
            return Rs.wrongFormat(fieldName);
        } else if (exception instanceof MissingServletRequestParameterException) {
            // 方法参数未传递
            handleError(request, exception);
            final String fieldName =
                    ((MissingServletRequestParameterException) exception).getParameterName();
            return Rs.notPresent(fieldName);
        } else if (exception instanceof HttpMessageNotReadableException) {
            // 请求体为空或请求体解析失败
            if (exception.getCause() instanceof JSONException) {
                handleError(request, (JSONException) exception.getCause());
                return Rs.wrongFormat("json");
            }
            handleError(request, exception);
            return Rs.notPresent("请求体");
        } else if (exception instanceof HttpMediaTypeNotSupportedException) {
            // 请求Content-type不支持
            handleError(request, exception);
            return Rs.notSupportContentType(request.getContentType());
        } else if (exception instanceof HttpRequestMethodNotSupportedException) {
            // 请求方式不支持
            handleError(request, exception);
            return Rs.notSupportMethod(request.getMethod());
        } else if (exception instanceof MyBatisSystemException) {
            final Throwable cause = exception.getCause();
            if (cause instanceof TooManyResultsException) {
                // 返回结果过多
                handleError(request, exception);
                return Rs.tooManyResults(request.getMethod());
            }
        } else if (exception instanceof ConstraintViolationException) {
            return Rs.fail(
                    "提交信息有误:<br/>"
                            .concat(
                                    ((ConstraintViolationException) exception)
                                            .getConstraintViolations().stream()
                                            .map(ConstraintViolation::getMessage)
                                            .collect(Collectors.joining("<br/>"))));
        } else if (exception instanceof DataAccessException) {
            // 处理该异常主要便于开发快速定位问题
            final Throwable cause = exception.getCause();
            if (cause instanceof SQLException) {
                handleError(request, (SQLException) cause);
                return Rs.fail(ResultCode.ERROR);
            }
        }
        handleError(request, exception);
        return Rs.fail();
    }

    @SuppressWarnings("unused")
    private void handleError(HttpServletRequest request, Exception exception) {
        log.error(exception.getMessage(), exception);
        request.setAttribute("error", exception.getMessage());
    }
}
